package org.yzh.scheduler.schedule;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.yzh.scheduler.mapper.JobLogMapper;
import org.yzh.scheduler.mapper.ScheduleJobMapper;
import org.yzh.scheduler.model.entity.JobLogDO;
import org.yzh.scheduler.model.entity.ScheduleJobDO;

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.LocalDateTime;
import java.util.Iterator;
import java.util.Map;

@Component
public class RemoteHttpTrigger {

    private static final Logger log = LoggerFactory.getLogger(RemoteHttpTrigger.class);

    @Autowired
    private ScheduleJobMapper scheduleJobMapper;

    @Autowired
    private JobLogMapper jobLogMapper;

    @Autowired
    private ObjectMapper objectMapper;

    @Autowired
    private HttpClient httpClient;

    @Autowired
    @Qualifier("domain")
    private String domain;

    public boolean trigger(String name, String group) {
        ScheduleJobDO job = scheduleJobMapper.get(name, group);
        if (job == null) return false;

        JobLogDO jobLog = new JobLogDO();

        jobLog.setJobId(job.getId());
        jobLog.setJobName(job.getJobName());
        jobLog.setJobGroup(job.getJobGroup());

        jobLog.setMethod(job.getMethod());
        jobLog.setUrl(job.getUrl());
        jobLog.setContentType(job.getContentType());
        jobLog.setParameters(job.getParameters());

        jobLog.setTriggerTime(LocalDateTime.now());
        jobLog.setTriggerCode(-1);

        jobLogMapper.insert(jobLog);

        triggerInternal(jobLog);
        return true;
    }

    private void triggerInternal(JobLogDO jobLog) {
        HttpRequest request = buildRequest(jobLog);
        httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString())
                .thenAccept(httpResponse -> {
                    jobLog.setTriggerCode(httpResponse.statusCode());
                    jobLog.setTriggerMsg(httpResponse.body());
                    jobLogMapper.update(jobLog);
                })
                .exceptionally(e -> {
                    log.warn("Log id:{}, name:{}, group:{}, exception:{}", jobLog.getId(), jobLog.getJobName(), jobLog.getJobGroup(), e.getClass().getName());
                    jobLog.setTriggerMsg(ExceptionUtils.getStackTrace(e));
                    if (jobLog.getTriggerCode() == null)
                        jobLog.setTriggerCode(0);
                    jobLogMapper.update(jobLog);
                    return null;
                });
    }

    private HttpRequest buildRequest(JobLogDO jobLog) {
        String callbackUrl = domain + "callback/" + JobLogDO.toKey(jobLog);

        String method = jobLog.getMethod();
        String contentType = jobLog.getContentType();
        String url = jobLog.getUrl();
        String parameters = jobLog.getParameters();

        StringBuilder urlBuilder = new StringBuilder(url.length() + callbackUrl.length() + 10);
        urlBuilder.append(url);
        if (url.contains("?"))
            urlBuilder.append('&');
        else
            urlBuilder.append('?');
        urlBuilder.append("callbackUrl=").append(URLEncoder.encode(callbackUrl, StandardCharsets.UTF_8));

        if ("GET".equals(method)) {
            urlBuilder = toURLParameters(parameters, urlBuilder.append('&'));

        } else if (MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(contentType)) {
            parameters = toURLParameters(parameters);
        }

        HttpRequest.Builder builder = HttpRequest.newBuilder();
        builder.timeout(Duration.ofSeconds(10));
        builder.uri(URI.create(urlBuilder.toString()));
        builder.header("Content-Type", contentType);
        builder.method(method, parameters == null ? HttpRequest.BodyPublishers.noBody() : HttpRequest.BodyPublishers.ofString(parameters));
        return builder.build();
    }

    private String toURLParameters(String parameters) {
        StringBuilder result = new StringBuilder(parameters.length());
        toURLParameters(parameters, result);
        return result.toString();
    }

    private StringBuilder toURLParameters(String parameters, StringBuilder result) {
        if (parameters == null || parameters.length() < 7)
            return result;
        try {
            JsonNode jsonNode = objectMapper.readTree(parameters);
            Iterator<Map.Entry<String, JsonNode>> fields = jsonNode.fields();
            while (fields.hasNext()) {
                Map.Entry<String, JsonNode> entry = fields.next();
                result.append(entry.getKey()).append("=").append(URLEncoder.encode(entry.getValue().asText(), StandardCharsets.UTF_8)).append("&");
            }
            return result;
        } catch (JsonProcessingException e) {
            throw new RuntimeException(e);
        }
    }
}